package com.paishen.common.okhttp;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.google.gson.Gson;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.Map;

@SuppressWarnings({"rawtypes", "unchecked", "unused"})
public class OkHttpClientManager {

    private static final String TAG = "OkHttp";
    public static final String USER_AGENT = "User-Agent";
    public String USER_AGENT_DEFAULT_VALUE = "";
    private boolean DEBUG = true;


    public static final MediaType MEDIA_TYPE_STREAM = MediaType.parse("application/octet-stream;charset=utf-8");
    public static final MediaType MEDIA_TYPE_STRING = MediaType.parse("text/plain;charset=utf-8");

    private static final OkHttpClientManager mOkHttpClientManager = new OkHttpClientManager();
    private final OkHttpClient okhttpClient;
    private final Gson gson;
    public final Handler mDelivery;

    public synchronized static OkHttpClientManager getInstance() {
        return mOkHttpClientManager;
    }

    public OkHttpClientManager() {
        okhttpClient = new OkHttpClient();
        gson = new Gson();
        mDelivery = new Handler(Looper.getMainLooper());
    }

    public void init(Context context, String userAgentDefaultValue, boolean debug) {
        // cookie enabled
        CookieManager cookieManager = new CookieManager(new PersistentCookieStore(context), CookiePolicy.ACCEPT_ORIGINAL_SERVER);
        okhttpClient.setCookieHandler(cookieManager);

        USER_AGENT_DEFAULT_VALUE = userAgentDefaultValue;
        DEBUG = debug;
    }

    public void download(Object tag, String url, final File local, final OkCallbackAdapter okCallbackAdapter) {
        if (url == null || local == null || okCallbackAdapter == null) {
            throw new IllegalArgumentException("download empty args");
        }

        final Request request = new Request.Builder().tag(tag).url(url).build();

        OkHttpClient clone = OkProgressHelper.addProgressResponseListener(okhttpClient, new OkCallbackAdapter<String>() {
            @Override
            public void onProgress(final long bytesWrite, final long contentLength, final boolean done) {
                mDelivery.post(new Runnable() {
                    @Override
                    public void run() {
                        okCallbackAdapter.onProgress(bytesWrite, contentLength, done);
                    }
                });
            }
        });

        clone.newCall(request).enqueue(new Callback() {
            @Override
            public void onResponse(Response response) {
                InputStream is = null;
                BufferedOutputStream bfos = null;
                try {
                    is = response.body().byteStream();
                    bfos = new BufferedOutputStream(new FileOutputStream(local));
                    byte[] buf = new byte[1024 * 8];
                    int len;
                    while ((len = is.read(buf)) != -1) {
                        bfos.write(buf, 0, len);
                    }
                    bfos.flush();

                    mDelivery.post(new Runnable() {
                        @Override
                        public void run() {
                            okCallbackAdapter.onResponse(local.getAbsolutePath(), true);
                        }
                    });
                } catch (final IOException e) {
                    mDelivery.post(new Runnable() {
                        @Override
                        public void run() {
                            okCallbackAdapter.onFailure(request, e);
                        }
                    });
                } finally {
                    try {
                        if (is != null)
                            is.close();
                        if (bfos != null)
                            bfos.close();
                    } catch (IOException e) {
                    }
                }
            }

            @Override
            public void onFailure(final Request request, final IOException e) {
                mDelivery.post(new Runnable() {
                    @Override
                    public void run() {
                        okCallbackAdapter.onFailure(request, e);
                    }
                });
            }
        });
    }

    public void upload(Object tag, String url, File file, final OkCallbackAdapter okCallbackAdapter) {
        if (url == null || file == null || okCallbackAdapter == null) {
            throw new IllegalArgumentException("post empty args");
        }

        RequestBody requestBody = new MultipartBuilder().type(MultipartBuilder.FORM)
                .addFormDataPart("file", file.getName(), RequestBody.create(MEDIA_TYPE_STREAM, file)).build();

        final Request request = new Request.Builder().url(url).post(OkProgressHelper.addProgressRequestListener(requestBody, new OkCallbackAdapter<String>() {
            @Override
            public void onProgress(final long bytesWrite, final long contentLength, final boolean done) {
                mDelivery.post(new Runnable() {
                    @Override
                    public void run() {
                        okCallbackAdapter.onProgress(bytesWrite, contentLength, done);
                    }
                });
            }
        })).build();
        if (DEBUG) Log.i(TAG, "post url: url\nfile:" + file.getPath());
        okhttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onResponse(Response response) {
                try {
                    final String result = response.body().string();
                    if (okCallbackAdapter.mType == String.class) {
                        mDelivery.post(new Runnable() {
                            @Override
                            public void run() {
                                okCallbackAdapter.onResponse(result, true);
                            }
                        });
                    } else {
                        final Object o = gson.fromJson(result, okCallbackAdapter.mType);
                        mDelivery.post(new Runnable() {
                            @Override
                            public void run() {
                                okCallbackAdapter.onResponse(o, true);
                            }
                        });
                    }
                } catch (final Exception e) {
                    mDelivery.post(new Runnable() {
                        @Override
                        public void run() {
                            okCallbackAdapter.onFailure(request, e);
                        }
                    });
                }
            }

            @Override
            public void onFailure(final Request request, final IOException e) {
                mDelivery.post(new Runnable() {
                    @Override
                    public void run() {
                        okCallbackAdapter.onFailure(request, e);
                    }
                });
            }
        });
    }

    public void request(final String url, final Request request, final OkCallbackAdapter okCallbackAdapter) {
        if (url == null || request == null || okCallbackAdapter == null) {
            throw new IllegalArgumentException("post empty args");
        }
        if (DEBUG) Log.i(TAG, "post request:\n" + request);
        okhttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onResponse(Response response) {
                try {
                    final String result = response.body().string();
                    if (DEBUG) Log.i(TAG, "onResponse\n" + result);
                    if (okCallbackAdapter.mType == String.class) {
                        mDelivery.post(new Runnable() {
                            @Override
                            public void run() {
                                okCallbackAdapter.onResponse(result, true);
                            }
                        });
                    } else {
                        final Object o = gson.fromJson(result, okCallbackAdapter.mType);
                        mDelivery.post(new Runnable() {
                            @Override
                            public void run() {
                                okCallbackAdapter.onResponse(o, true);
                            }
                        });
                    }
                } catch (final Exception e) {
                    mDelivery.post(new Runnable() {
                        @Override
                        public void run() {
                            okCallbackAdapter.onFailure(request, e);
                        }
                    });
                }
            }

            @Override
            public void onFailure(final Request request, final IOException e) {
                mDelivery.post(new Runnable() {
                    @Override
                    public void run() {
                        okCallbackAdapter.onFailure(request, e);
                    }
                });
            }
        });
    }


    public void post(Object tag, String url, Map<String, String> params, final OkCallbackAdapter okCallbackAdapter) {
        if (url == null || params == null || okCallbackAdapter == null) {
            throw new IllegalArgumentException("post empty args");
        }

        FormEncodingBuilder requestBodyBuilder = new FormEncodingBuilder();
        for (String key : params.keySet()) {
            requestBodyBuilder.add(key, params.get(key));
        }

        RequestBody requestBody = requestBodyBuilder.build();
        final Request request = new Request.Builder().url(url).tag(tag).post(requestBody)
                .header(USER_AGENT, USER_AGENT_DEFAULT_VALUE).build();

        if (DEBUG) Log.i(TAG, "post url:" + url + "\n" + params);
        okhttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onResponse(Response response) {
                try {
                    final String result = response.body().string();
                    if (DEBUG) Log.i(TAG, "onResponse\n" + result);
                    if (okCallbackAdapter.mType == String.class) {
                        mDelivery.post(new Runnable() {
                            @Override
                            public void run() {
                                okCallbackAdapter.onResponse(result, true);
                            }
                        });
                    } else {
                        final Object o = gson.fromJson(result, okCallbackAdapter.mType);
                        mDelivery.post(new Runnable() {
                            @Override
                            public void run() {
                                okCallbackAdapter.onResponse(o, true);
                            }
                        });
                    }
                } catch (final Exception e) {
                    if (DEBUG) Log.e(TAG, "onFailure\n" + e.getMessage());
                    mDelivery.post(new Runnable() {
                        @Override
                        public void run() {
                            okCallbackAdapter.onFailure(request, e);
                        }
                    });
                }
            }

            @Override
            public void onFailure(final Request request, final IOException e) {
                mDelivery.post(new Runnable() {
                    @Override
                    public void run() {
                        okCallbackAdapter.onFailure(request, e);
                    }
                });
            }
        });
    }

    public void cancel(Object tag) {
        okhttpClient.cancel(tag);
    }
}
